133 lines
3.4 KiB
Go
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>
|
|
`
|