nub/internal/scaffold/init.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

133 lines
3.4 KiB
Go

// Package scaffold legt die Grundausstattung für ein Projekt an
// (`nub init` / `/init`): .nub/config.toml mit einer kommentierten
// Beispiel-Config und ein minimalistisches AGENTS.md. Von TUI und CLI
// gemeinsam genutzt.
package scaffold
import (
"fmt"
"os"
"path/filepath"
"nub/internal/session"
)
// Result protokolliert, was Init tatsächlich angelegt bzw. übersprungen hat.
type Result struct {
ConfigPath string
ConfigCreated bool
AgentsPath string
AgentsCreated bool
}
// Init legt .nub/config.toml und AGENTS.md an, falls sie noch nicht
// existieren. Bestehende Dateien werden nie überschrieben — Init meldet in
// Result nur, was es tatsächlich geschrieben hat.
func Init(repoRoot string) (Result, error) {
var res Result
nubDir := filepath.Join(repoRoot, ".nub")
if err := os.MkdirAll(nubDir, 0o700); err != nil {
return res, fmt.Errorf("scaffold: .nub anlegen: %w", err)
}
if err := session.EnsureGitExclude(repoRoot); err != nil {
return res, fmt.Errorf("scaffold: git exclude: %w", err)
}
res.ConfigPath = filepath.Join(nubDir, "config.toml")
created, err := writeIfAbsent(res.ConfigPath, exampleConfigTOML, 0o600)
if err != nil {
return res, fmt.Errorf("scaffold: config.toml: %w", err)
}
res.ConfigCreated = created
res.AgentsPath = filepath.Join(repoRoot, "AGENTS.md")
created, err = writeIfAbsent(res.AgentsPath, exampleAgentsMD, 0o644)
if err != nil {
return res, fmt.Errorf("scaffold: AGENTS.md: %w", err)
}
res.AgentsCreated = created
return res, nil
}
func writeIfAbsent(path, content string, perm os.FileMode) (bool, error) {
if _, err := os.Stat(path); err == nil {
return false, nil // existiert schon -> nicht anfassen
} else if !os.IsNotExist(err) {
return false, err
}
if err := os.WriteFile(path, []byte(content), perm); err != nil {
return false, err
}
return true, nil
}
const exampleConfigTOML = `version = 1
[model]
default = "gpt-4o-mini"
endpoint = "openai"
[[endpoint]]
name = "openai"
base_url = "https://api.openai.com/v1"
# "env:VARNAME" liest den Key aus einer Umgebungsvariable; alternativ direkt
# den Key eintragen, oder "none" für einen lokalen Endpoint ohne Key.
api_key = "env:OPENAI_API_KEY"
# [[endpoint]]
# name = "local"
# base_url = "http://localhost:11434/v1"
# api_key = "none"
# [endpoint.caps]
# parallel_tool_calls = false
[context]
files = ["AGENTS.md", "REPOMAP.md"]
walk_up = true
max_tokens = 20000
[skills]
paths = ["~/.nub/skills", ".nub/skills"]
# Pro Tool: "auto" (ohne Rückfrage), "ask" (Rückfrage in der TUI, im
# Print-Modus ein harter Fehler) oder "deny" (nie). deny_paths/deny_bash
# überstimmen den Modus immer, auch bei "auto".
[permissions]
read = "auto"
glob = "auto"
grep = "auto"
write = "ask"
edit = "ask"
bash = "ask"
deny_paths = [".git/**", "**/.env", "**/id_rsa*"]
deny_bash = ["rm -rf /", "git push --force*"]
# [[mcp]]
# name = "github"
# command = "gh-mcp-server"
# tools = ["create_issue", "get_pull_request"]
# [profiles]
# review = ["github"]
# default = []
`
const exampleAgentsMD = `# Agent Instructions
Kurze, projektspezifische Hinweise für nub. Diese Datei landet automatisch
im System-Prompt jeder Session.
## Projekt
<Was macht dieses Projekt, in ein bis zwei Sätzen?>
## Konventionen
- <z.B. Codestil, Teststrategie, Verzeichnisstruktur>
## Vorsicht bei
- <z.B. generierte Dateien, sensible Verzeichnisse, kritische Module>
`