feat(canonical): validate profile name, apply env-var substitution before render

This commit is contained in:
2026-05-23 11:55:26 +01:00
parent 5e35dc47fe
commit 50059d7517
14 changed files with 423 additions and 23 deletions
+24 -14
View File
@@ -23,6 +23,7 @@ func NewInit(reg *adapter.Registry) *cobra.Command {
home string
from []string
noPrompt bool
force bool
)
cmd := &cobra.Command{
Use: "init",
@@ -42,6 +43,10 @@ func NewInit(reg *adapter.Registry) *cobra.Command {
if err = os.MkdirAll(r, 0o750); err != nil {
return err
}
// Issue 1: refuse overwrite without --force
if _, statErr := os.Stat(filepath.Join(r, "config.yaml")); statErr == nil && !force {
return fmt.Errorf("canonical root %s already initialised (use --force to re-import; existing skills/agents/mcp will be merged)", r)
}
candidates := reg.DetectedNames()
if len(candidates) == 0 {
return fmt.Errorf("no harnesses detected under %s", home)
@@ -77,6 +82,7 @@ func NewInit(reg *adapter.Registry) *cobra.Command {
cmd.Flags().StringVar(&home, "home", "", "home dir (default $HOME)")
cmd.Flags().StringSliceVar(&from, "from", nil, "import from specific adapter(s), skip prompt")
cmd.Flags().BoolVar(&noPrompt, "no-prompt", false, "import from all detected adapters without prompting")
cmd.Flags().BoolVar(&force, "force", false, "re-import even if canonical root already initialised")
return cmd
}
@@ -153,6 +159,18 @@ func runImport(cmd *cobra.Command, reg *adapter.Registry, root, home string, pic
return nil
}
// importedProfileBody is the placeholder written to profiles/imported.yaml on first init.
// Written using escape sequences to avoid literal newlines in string literals.
const importedProfileBody = "# harness-sync profile \xe2\x80\x94 generated by init\n" +
"# Edit the gateway block below to point at your LLM gateway before running\n" +
"# `harness-sync apply`. The dummy token is fine for most local gateways.\n" +
"name: imported\n" +
"gateway:\n" +
" url: \"\" # \xe2\x86\x90 set this\n" +
" token: \"dummy\"\n" +
" default_model: \"\" # \xe2\x86\x90 set this\n" +
"models: []\n"
func writeCanonical(root string, b *canonical.Bundle, picked []string) error {
for _, sub := range []string{"profiles", "skills", "agents", "instructions"} {
if err := os.MkdirAll(filepath.Join(root, sub), 0o750); err != nil {
@@ -171,20 +189,12 @@ func writeCanonical(root string, b *canonical.Bundle, picked []string) error {
return err
}
profBody, err := render.YAML(canonical.Profile{
Name: "imported",
Gateway: canonical.Gateway{
URL: "https://gateway.local",
Token: "dummy",
DefaultModel: "claude-sonnet-4-6",
},
Models: []canonical.Model{{ID: "claude-sonnet-4-6", Alias: "sonnet"}},
})
if err != nil {
return err
}
if err = os.WriteFile(filepath.Join(root, "profiles", "imported.yaml"), profBody, 0o600); err != nil {
return err
// Issue 2: write placeholder profile only if it doesn't exist yet (preserve user edits on re-init).
profPath := filepath.Join(root, "profiles", "imported.yaml")
if _, statErr := os.Stat(profPath); os.IsNotExist(statErr) {
if err = os.WriteFile(profPath, []byte(importedProfileBody), 0o600); err != nil {
return err
}
}
if len(b.MCP.Servers) > 0 {