a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
154 lines
5.1 KiB
Go
154 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/zzet/gortex/internal/agents"
|
|
"github.com/zzet/gortex/internal/agents/claudecode"
|
|
"github.com/zzet/gortex/internal/profiles"
|
|
)
|
|
|
|
// instructions.go is the `gortex instructions` command tree — the CLI
|
|
// front end for instruction profiles (internal/profiles): named
|
|
// bundles of instructions body + MCP tool preset + skills subset +
|
|
// hook-verbosity tier, generated from one table. `switch` atomically
|
|
// repoints the @-included active.md copy; agents pick the change up at
|
|
// their next session start.
|
|
|
|
var instructionsCmd = &cobra.Command{
|
|
Use: "instructions",
|
|
Short: "Manage instruction profiles (guidance depth per machine)",
|
|
Long: "Manage the machine's instruction profiles. A profile bundles the " +
|
|
"agent-facing instructions body (@-included from the rules file), the " +
|
|
"MCP tool preset sessions default to, the installed skills subset, and " +
|
|
"the hook-verbosity tier — all generated from one table so they cannot " +
|
|
"drift. Switching applies to NEW sessions only: instructions, " +
|
|
"tools/list, and skills are all loaded at session start.",
|
|
}
|
|
|
|
var instructionsListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List profiles and show which one is active",
|
|
Args: cobra.NoArgs,
|
|
RunE: runInstructionsList,
|
|
}
|
|
|
|
var instructionsShowCmd = &cobra.Command{
|
|
Use: "show <profile>",
|
|
Short: "Print one profile's instructions body",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runInstructionsShow,
|
|
}
|
|
|
|
var instructionsSwitchCmd = &cobra.Command{
|
|
Use: "switch <profile>",
|
|
Short: "Make a profile active (takes effect for new sessions)",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runInstructionsSwitch,
|
|
}
|
|
|
|
var instructionsRegenCmd = &cobra.Command{
|
|
Use: "regen",
|
|
Short: "Regenerate the profile files from this binary (keeps the active selection)",
|
|
Args: cobra.NoArgs,
|
|
RunE: runInstructionsRegen,
|
|
}
|
|
|
|
func init() {
|
|
instructionsCmd.AddCommand(instructionsListCmd)
|
|
instructionsCmd.AddCommand(instructionsShowCmd)
|
|
instructionsCmd.AddCommand(instructionsSwitchCmd)
|
|
instructionsCmd.AddCommand(instructionsRegenCmd)
|
|
rootCmd.AddCommand(instructionsCmd)
|
|
}
|
|
|
|
func runInstructionsList(cmd *cobra.Command, _ []string) error {
|
|
dir := profiles.DefaultDir()
|
|
active := profiles.ActiveName(dir)
|
|
cmd.Printf("Instruction profiles (dir: %s)\n\n", dir)
|
|
for _, p := range profiles.Table() {
|
|
marker := " "
|
|
if p.Name == active {
|
|
marker = "*"
|
|
}
|
|
preset := p.ToolPreset
|
|
if preset == "" {
|
|
preset = "(client default)"
|
|
}
|
|
skills := "all"
|
|
if p.Skills != nil {
|
|
skills = fmt.Sprintf("%d", len(p.Skills))
|
|
}
|
|
cmd.Printf("%s %-13s %s\n", marker, p.Name, p.Summary)
|
|
cmd.Printf(" body: %4d bytes · tool preset: %s · skills: %s · hook tier: %s\n",
|
|
len(p.Body()), preset, skills, p.HookTier)
|
|
}
|
|
cmd.Printf("\n* = active. Switch with `gortex instructions switch <name>` — applies to NEW sessions only.\n")
|
|
return nil
|
|
}
|
|
|
|
func runInstructionsShow(cmd *cobra.Command, args []string) error {
|
|
name := strings.TrimSpace(args[0])
|
|
p, ok := profiles.ByName(name)
|
|
if !ok {
|
|
return fmt.Errorf("unknown instruction profile %q (known: %s)", name, strings.Join(profiles.Names(), ", "))
|
|
}
|
|
cmd.Print(p.Body())
|
|
return nil
|
|
}
|
|
|
|
func runInstructionsSwitch(cmd *cobra.Command, args []string) error {
|
|
name := strings.TrimSpace(args[0])
|
|
dir := profiles.DefaultDir()
|
|
p, err := profiles.Switch(dir, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.Printf("Switched to the %q instruction profile (active copy: %s/%s).\n", p.Name, dir, profiles.ActiveFileName)
|
|
|
|
// Reconcile the installed skills with the profile's subset. Best
|
|
// effort: a missing home or an unwritable skills dir downgrades to
|
|
// a warning, the profile switch itself has already landed.
|
|
if home, err := os.UserHomeDir(); err == nil && home != "" {
|
|
actions, err := claudecode.SyncGlobalSkills(cmd.ErrOrStderr(), home, p.Skills, agents.ApplyOpts{})
|
|
if err != nil {
|
|
fmt.Fprintf(cmd.ErrOrStderr(), "warning: skills sync incomplete: %v\n", err)
|
|
} else {
|
|
installed, removed := 0, 0
|
|
for _, a := range actions {
|
|
switch a.Action {
|
|
case agents.ActionCreate:
|
|
installed++
|
|
case agents.ActionDelete:
|
|
removed++
|
|
}
|
|
}
|
|
if installed+removed > 0 {
|
|
cmd.Printf("Skills reconciled: %d installed, %d removed.\n", installed, removed)
|
|
}
|
|
}
|
|
// Nudge when the pointer block was never installed — the
|
|
// profile only reaches agents through the @-include.
|
|
if md, err := os.ReadFile(claudecode.UserClaudeMdPath(home)); err != nil || !strings.Contains(string(md), agents.GlobalRulesStartMarker) {
|
|
cmd.Printf("Note: no Gortex rule block found in ~/.claude/CLAUDE.md — run `gortex install` to wire the @-include pointer.\n")
|
|
}
|
|
}
|
|
|
|
cmd.Printf("\nTakes effect for NEW sessions only: the instructions @-include, the MCP tools/list, and skills are all loaded at session start — running sessions keep their current surface.\n")
|
|
return nil
|
|
}
|
|
|
|
func runInstructionsRegen(cmd *cobra.Command, _ []string) error {
|
|
dir := profiles.DefaultDir()
|
|
if err := profiles.Generate(dir); err != nil {
|
|
return err
|
|
}
|
|
cmd.Printf("Regenerated %d profile(s) in %s (active: %s — unchanged).\n",
|
|
len(profiles.Table()), dir, profiles.ActiveName(dir))
|
|
return nil
|
|
}
|