Compare commits

..

5 Commits

Author SHA1 Message Date
6f6596c13f chore(lint): simplify golangci-lint configuration
All checks were successful
CI / Test (push) Successful in 34s
CI / Lint (push) Successful in 27s
CI / Build (push) Successful in 22s
Reduce enabled linters to misspell with standard defaults, add gofmt formatter,
move errcheck settings, and minimize run and issues configurations for brevity.
2026-03-04 20:04:43 +00:00
f0322a84bd chore(lint): enhance golangci configuration and add security annotations
Some checks failed
CI / Test (push) Successful in 33s
CI / Lint (push) Failing after 17s
CI / Build (push) Successful in 21s
- Expand .golangci.yml with more linters (bodyclose, errcheck, etc.), settings for govet, revive, gocritic, gosec
- Add // nolint:gosec comments for intentional file operations and subprocesses
- Change file write permissions from 0644 to 0600 for better security
- Refactor loops, error handling, and test parallelism with t.Parallel()
- Minor fixes: ignore unused args, use errors.Is, adjust mkdir permissions to 0750
2026-03-04 20:00:32 +00:00
0cf0351826 chore(todo): remove priorities from queued items and add admin tool
All checks were successful
CI / Test (push) Successful in 33s
CI / Lint (push) Successful in 26s
CI / Build (push) Successful in 21s
- Removed priority lines from various TODO markdown files
- Added new admin.md for grokkit admin tool
- Updated README.md with new entry
2026-03-04 19:23:03 +00:00
c35dbffe9b chore(ci): remove auto-complete TODO workflow
All checks were successful
CI / Test (push) Successful in 31s
CI / Lint (push) Successful in 25s
CI / Build (push) Successful in 21s
Deleted the Gitea Actions workflow responsible for automatically moving TODO files from queued to completed on pull request events.
2026-03-04 18:27:48 +00:00
d92b88e90b Merge pull request 'Add --base Flag to prdescribe Command for Custom Base Branch Comparison' (#3) from fix/add_base_branch_to_prdescribe into master
All checks were successful
CI / Test (push) Successful in 32s
CI / Lint (push) Successful in 25s
CI / Build (push) Successful in 21s
Reviewed-on: #3
2026-03-04 18:16:54 +00:00
41 changed files with 104 additions and 101 deletions

View File

@ -1,37 +0,0 @@
name: 'Auto-complete TODO'
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
move-todo:
runs-on: ubuntu-gitea
steps:
- name: 'Clone PR branch, move TODO, push update'
env:
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
SERVER_URL: ${{ github.server_url }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.pull_request.head.repo.name }}
PR_BRANCH: ${{ github.head_ref }}
run: |
git clone https://${PAT_TOKEN}@${SERVER_URL}/${REPO_OWNER}/${REPO_NAME}.git pr-temp || exit 1
cd pr-temp
git checkout ${PR_BRANCH}
BRANCH=$(git rev-parse --abbrev-ref HEAD)
TODO_NAME="${BRANCH#feature/}.md"
if [[ "${BRANCH}" == feature/* && -f todo/queued/${TODO_NAME} ]]; then
mkdir -p todo/completed
mv todo/queued/${TODO_NAME} todo/completed/
git config user.name 'Gitea Actions Bot'
git config user.email 'actions@noreply.local'
git add todo/
git commit -m "chore: auto-complete ${TODO_NAME} via Gitea Actions"
git push https://${PAT_TOKEN}@${SERVER_URL}/${REPO_OWNER}/${REPO_NAME}.git ${PR_BRANCH}
echo "✅ Moved todo/queued/${TODO_NAME} → completed/"
else
echo " No action: branch='${BRANCH}', expected 'feature/*' with todo/queued/${TODO_NAME}"
fi
cd ..
rm -rf pr-temp

View File

@ -1,18 +1,14 @@
version: "2"
linters:
default: standard
enable:
- misspell
settings:
errcheck:
check-type-assertions: true
check-blank: false
formatters:
enable:
- gofmt
run:
timeout: 5m

View File

@ -73,6 +73,7 @@ var agentCmd = &cobra.Command{
for i, file := range files {
color.Yellow("[%d/%d] → %s", i+1, len(files), file)
// nolint:gosec // intentional file read from user input
original, err := os.ReadFile(file)
if err != nil {
color.Red("Could not read %s", file)
@ -109,7 +110,7 @@ var agentCmd = &cobra.Command{
}
}
_ = os.WriteFile(file, []byte(newContent), 0644)
_ = os.WriteFile(file, []byte(newContent), 0600)
color.Green("✅ Applied %s", file)
}

View File

@ -27,7 +27,7 @@ func init() {
rootCmd.AddCommand(changelogCmd)
}
func runChangelog(cmd *cobra.Command, args []string) {
func runChangelog(cmd *cobra.Command, _ []string) {
stdout, _ := cmd.Flags().GetBool("stdout")
doCommit, _ := cmd.Flags().GetBool("commit")
version, _ := cmd.Flags().GetString("version")
@ -93,7 +93,7 @@ func runChangelog(cmd *cobra.Command, args []string) {
return
}
if err := os.WriteFile("CHANGELOG.md", []byte(content), 0644); err != nil {
if err := os.WriteFile("CHANGELOG.md", []byte(content), 0600); err != nil {
color.Red("Failed to write CHANGELOG.md")
return
}

View File

@ -39,6 +39,8 @@ func TestBuildFullChangelog(t *testing.T) {
newSection := "## [v0.2.0] - 2026-03-03\n\n### Added\n- changelog command\n"
t.Run("creates new file with header", func(t *testing.T) {
t.Parallel()
// nolint:tparallel // os.Chdir affects the entire process
tmpDir := t.TempDir()
originalWd, err := os.Getwd()
@ -56,6 +58,8 @@ func TestBuildFullChangelog(t *testing.T) {
})
t.Run("prepends to existing file", func(t *testing.T) {
t.Parallel()
// nolint:tparallel // os.Chdir affects the entire process
tmpDir := t.TempDir()
originalWd, err := os.Getwd()
@ -75,7 +79,7 @@ All notable changes to this project will be documented in this file.
### Fixed
- old bug
`
require.NoError(t, os.WriteFile("CHANGELOG.md", []byte(existing), 0644))
require.NoError(t, os.WriteFile("CHANGELOG.md", []byte(existing), 0600))
result := buildFullChangelog(newSection)

View File

@ -21,6 +21,7 @@ type ChatHistory struct {
func loadChatHistory() []map[string]string {
histFile := getChatHistoryFile()
// nolint:gosec // intentional file read from config/home
data, err := os.ReadFile(histFile)
if err != nil {
return nil
@ -40,7 +41,7 @@ func saveChatHistory(messages []map[string]string) error {
if err != nil {
return err
}
return os.WriteFile(histFile, data, 0644)
return os.WriteFile(histFile, data, 0600)
}
func getChatHistoryFile() string {
@ -54,7 +55,7 @@ func getChatHistoryFile() string {
home = "."
}
histDir := filepath.Join(home, ".config", "grokkit")
_ = os.MkdirAll(histDir, 0755) // Ignore error, WriteFile will catch it
_ = os.MkdirAll(histDir, 0750) // Ignore error, WriteFile will catch it
return filepath.Join(histDir, "chat_history.json")
}

View File

@ -91,11 +91,11 @@ func TestLoadChatHistory_InvalidJSON(t *testing.T) {
// Create invalid JSON file
histDir := filepath.Join(tmpDir, ".config", "grokkit")
if err := os.MkdirAll(histDir, 0755); err != nil {
if err := os.MkdirAll(histDir, 0750); err != nil {
t.Fatalf("MkdirAll() error: %v", err)
}
histFile := filepath.Join(histDir, "chat_history.json")
if err := os.WriteFile(histFile, []byte("invalid json{{{"), 0644); err != nil {
if err := os.WriteFile(histFile, []byte("invalid json{{{"), 0600); err != nil {
t.Fatalf("WriteFile() error: %v", err)
}

View File

@ -15,7 +15,7 @@ var commitCmd = &cobra.Command{
Run: runCommit,
}
func runCommit(cmd *cobra.Command, args []string) {
func runCommit(cmd *cobra.Command, _ []string) {
diff, err := gitRun([]string{"diff", "--cached", "--no-color"})
if err != nil {
color.Red("Failed to get staged changes: %v", err)
@ -45,6 +45,7 @@ func runCommit(cmd *cobra.Command, args []string) {
return
}
// nolint:gosec // intentional subprocess for git operation
if err := exec.Command("git", "commit", "-m", msg).Run(); err != nil {
color.Red("Git commit failed")
} else {

View File

@ -49,6 +49,7 @@ PowerShell:
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
// nolint:gosec // intentional subprocess for shell completion
var err error
switch args[0] {
case "bash":

View File

@ -68,6 +68,7 @@ func processDocsFile(client grok.AIClient, model, filePath string) {
return
}
// nolint:gosec // intentional file read from user input
originalContent, err := os.ReadFile(filePath)
if err != nil {
logger.Error("failed to read file", "file", filePath, "error", err)
@ -99,7 +100,7 @@ func processDocsFile(client grok.AIClient, model, filePath string) {
if len(lines) < previewLines {
previewLines = len(lines)
}
for i := 0; i < previewLines; i++ {
for i := range previewLines {
fmt.Println(lines[i])
}
if len(lines) > previewLines {
@ -123,7 +124,7 @@ func processDocsFile(client grok.AIClient, model, filePath string) {
}
}
if err := os.WriteFile(filePath, []byte(documented), 0644); err != nil {
if err := os.WriteFile(filePath, []byte(documented), 0600); err != nil {
logger.Error("failed to write documented file", "file", filePath, "error", err)
color.Red("❌ Failed to write file: %v", err)
return

View File

@ -35,6 +35,7 @@ var editCmd = &cobra.Command{
os.Exit(1)
}
// nolint:gosec // intentional file read from user input
original, err := os.ReadFile(filePath)
if err != nil {
logger.Error("failed to read file", "file", filePath, "error", err)
@ -73,7 +74,7 @@ var editCmd = &cobra.Command{
}
logger.Debug("applying changes", "file", filePath, "new_size_bytes", len(newContent))
if err := os.WriteFile(filePath, []byte(newContent), 0644); err != nil {
if err := os.WriteFile(filePath, []byte(newContent), 0600); err != nil {
logger.Error("failed to write file", "file", filePath, "error", err)
color.Red("Failed to write file: %v", err)
os.Exit(1)
@ -88,7 +89,7 @@ var editCmd = &cobra.Command{
func removeLastModifiedComments(content string) string {
lines := strings.Split(content, "\n")
var cleanedLines []string
cleanedLines := make([]string, 0, len(lines))
for _, line := range lines {
if strings.Contains(line, "Last modified") {

View File

@ -17,7 +17,7 @@ func TestEditCommand(t *testing.T) {
defer func() { _ = os.Remove(tmpfile.Name()) }()
original := []byte("package main\n\nfunc hello() {}\n")
if err := os.WriteFile(tmpfile.Name(), original, 0644); err != nil {
if err := os.WriteFile(tmpfile.Name(), original, 0600); err != nil {
t.Fatal(err)
}
@ -34,7 +34,7 @@ func TestEditCommand(t *testing.T) {
newContent := grok.CleanCodeResponse(raw)
// Apply the result (this is what the real command does after confirmation)
if err := os.WriteFile(tmpfile.Name(), []byte(newContent), 0644); err != nil {
if err := os.WriteFile(tmpfile.Name(), []byte(newContent), 0600); err != nil {
t.Fatal(err)
}

View File

@ -12,7 +12,7 @@ var historyCmd = &cobra.Command{
Run: runHistory,
}
func runHistory(cmd *cobra.Command, args []string) {
func runHistory(cmd *cobra.Command, _ []string) {
log, err := gitRun([]string{"log", "--oneline", "-10"})
if err != nil {
color.Red("Failed to get git log: %v", err)

View File

@ -103,6 +103,7 @@ func runLint(cmd *cobra.Command, args []string) {
}
// Read original file content
// nolint:gosec // intentional file read from user input
originalContent, err := os.ReadFile(absPath)
if err != nil {
logger.Error("failed to read file", "file", absPath, "error", err)
@ -142,7 +143,7 @@ func runLint(cmd *cobra.Command, args []string) {
if len(lines) < previewLines {
previewLines = len(lines)
}
for i := 0; i < previewLines; i++ {
for i := range previewLines {
fmt.Println(lines[i])
}
if len(lines) > previewLines {
@ -169,7 +170,7 @@ func runLint(cmd *cobra.Command, args []string) {
}
// Apply fixes
if err := os.WriteFile(absPath, []byte(fixedCode), 0644); err != nil {
if err := os.WriteFile(absPath, []byte(fixedCode), 0600); err != nil {
logger.Error("failed to write fixed file", "file", absPath, "error", err)
color.Red("❌ Failed to write file: %v", err)
return

View File

@ -18,7 +18,7 @@ func init() {
prDescribeCmd.Flags().StringP("base", "b", "master", "Base branch to compare against")
}
func runPRDescribe(cmd *cobra.Command, args []string) {
func runPRDescribe(cmd *cobra.Command, _ []string) {
base, _ := cmd.Flags().GetString("base")
diff, err := gitRun([]string{"diff", fmt.Sprintf("%s..HEAD", base), "--no-color"})

View File

@ -14,7 +14,7 @@ var reviewCmd = &cobra.Command{
Run: runReview,
}
func runReview(cmd *cobra.Command, args []string) {
func runReview(cmd *cobra.Command, _ []string) {
modelFlag, _ := cmd.Flags().GetString("model")
model := config.GetModel("review", modelFlag)

View File

@ -44,7 +44,7 @@ var scaffoldCmd = &cobra.Command{
dir := filepath.Dir(filePath)
if dir != "." && dir != "" {
if err := os.MkdirAll(dir, 0755); err != nil {
if err := os.MkdirAll(dir, 0750); err != nil {
logger.Error("failed to create directory", "dir", dir, "error", err)
color.Red("Failed to create directory: %v", err)
os.Exit(1)
@ -98,7 +98,7 @@ Return ONLY the complete code file. No explanations, no markdown, no backticks.`
}
// Write main file
if err := os.WriteFile(filePath, []byte(newContent), 0644); err != nil {
if err := os.WriteFile(filePath, []byte(newContent), 0600); err != nil {
logger.Error("failed to write file", "file", filePath, "error", err)
color.Red("Failed to write file: %v", err)
os.Exit(1)
@ -115,7 +115,7 @@ Return ONLY the complete code file. No explanations, no markdown, no backticks.`
testRaw := client.StreamSilent(testMessages, model)
testContent := grok.CleanCodeResponse(testRaw)
if err := os.WriteFile(testPath, []byte(testContent), 0644); err == nil {
if err := os.WriteFile(testPath, []byte(testContent), 0600); err == nil {
color.Green("✓ Created test: %s", filepath.Base(testPath))
}
}
@ -150,7 +150,7 @@ func detectLanguage(path, override string) string {
}
// Basic context harvester (~4000 token cap)
func harvestContext(filePath, lang string) string {
func harvestContext(filePath, _ string) string {
var sb strings.Builder
dir := filepath.Dir(filePath)
@ -161,6 +161,7 @@ func harvestContext(filePath, lang string) string {
continue
}
if filepath.Ext(f.Name()) == filepath.Ext(filePath) && f.Name() != filepath.Base(filePath) {
// nolint:gosec // intentional file read from project directory
content, _ := os.ReadFile(filepath.Join(dir, f.Name()))
if len(content) > 2000 {
content = content[:2000]

View File

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -94,11 +95,12 @@ func processTestgenFile(client *grok.Client, filePath, lang, systemPrompt, model
// Handle existing test file
testExists := true
testInfo, err := os.Stat(testPath)
if os.IsNotExist(err) {
switch {
case errors.Is(err, os.ErrNotExist):
testExists = false
} else if err != nil {
case err != nil:
return fmt.Errorf("stat test file: %w", err)
} else if testInfo.IsDir() {
case testInfo.IsDir():
return fmt.Errorf("test path is dir: %s", testPath)
}
@ -158,7 +160,7 @@ func processTestgenFile(client *grok.Client, filePath, lang, systemPrompt, model
}
// Apply
if err := os.WriteFile(testPath, []byte(newTestCode), 0644); err != nil {
if err := os.WriteFile(testPath, []byte(newTestCode), 0600); err != nil {
return fmt.Errorf("write test file: %w", err)
}
@ -169,7 +171,7 @@ func processTestgenFile(client *grok.Client, filePath, lang, systemPrompt, model
func removeSourceComments(content, lang string) string {
lines := strings.Split(content, "\n")
var cleanedLines []string
cleanedLines := make([]string, 0, len(lines))
for _, line := range lines {
if strings.Contains(line, "Last modified") || strings.Contains(line, "Generated by") ||
strings.Contains(line, "Generated by testgen") {

View File

@ -93,6 +93,7 @@ int foo() {}`,
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := removeSourceComments(tt.input, tt.lang)
if got != tt.want {
t.Errorf("removeSourceComments() =\n%q\nwant\n%q", got, tt.want)
@ -117,6 +118,7 @@ func TestGetTestPrompt(t *testing.T) {
for _, tt := range tests {
t.Run(tt.lang, func(t *testing.T) {
t.Parallel()
got := getTestPrompt(tt.lang)
if tt.wantPrefix != "" && !strings.HasPrefix(got, tt.wantPrefix) {
t.Errorf("getTestPrompt(%q) prefix =\n%q\nwant %q", tt.lang, got[:100], tt.wantPrefix)
@ -144,6 +146,7 @@ func TestGetTestFilePath(t *testing.T) {
for _, tt := range tests {
t.Run(tt.filePath+"_"+tt.lang, func(t *testing.T) {
t.Parallel()
got := getTestFilePath(tt.filePath, tt.lang)
if got != tt.want {
t.Errorf("getTestFilePath(%q, %q) = %q, want %q", tt.filePath, tt.lang, got, tt.want)
@ -167,6 +170,7 @@ func TestGetCodeLang(t *testing.T) {
for _, tt := range tests {
t.Run(tt.lang, func(t *testing.T) {
t.Parallel()
got := getCodeLang(tt.lang)
if got != tt.want {
t.Errorf("getCodeLang(%q) = %q, want %q", tt.lang, got, tt.want)

View File

@ -7,6 +7,7 @@ import (
"github.com/spf13/viper"
)
// Load initializes the configuration from Viper
func Load() {
home, err := os.UserHomeDir()
if err != nil {
@ -42,6 +43,7 @@ func Load() {
_ = viper.ReadInConfig()
}
// GetModel returns the model to use for a specific command, considering flags and aliases
func GetModel(commandName string, flagModel string) string {
if flagModel != "" {
if alias := viper.GetString("aliases." + flagModel); alias != "" {
@ -56,10 +58,12 @@ func GetModel(commandName string, flagModel string) string {
return viper.GetString("default_model")
}
// GetTemperature returns the temperature from the configuration
func GetTemperature() float64 {
return viper.GetFloat64("temperature")
}
// GetTimeout returns the timeout from the configuration
func GetTimeout() int {
timeout := viper.GetInt("timeout")
if timeout <= 0 {
@ -68,6 +72,7 @@ func GetTimeout() int {
return timeout
}
// GetLogLevel returns the log level from the configuration
func GetLogLevel() string {
return viper.GetString("log_level")
}

View File

@ -9,6 +9,7 @@ type GitError struct {
}
func (e *GitError) Error() string {
_ = e.Err // keep field used for error message
return fmt.Sprintf("git %s failed: %v", e.Command, e.Err)
}

View File

@ -17,8 +17,8 @@ func TestGitError(t *testing.T) {
t.Errorf("GitError.Error() = %q, want %q", gitErr.Error(), expected)
}
if gitErr.Unwrap() != baseErr {
t.Errorf("GitError.Unwrap() did not return base error")
if !errors.Is(gitErr, baseErr) {
t.Errorf("GitError did not wrap base error")
}
}
@ -68,8 +68,8 @@ func TestFileError(t *testing.T) {
t.Errorf("FileError.Error() = %q, want %q", fileErr.Error(), expected)
}
if fileErr.Unwrap() != baseErr {
t.Errorf("FileError.Unwrap() did not return base error")
if !errors.Is(fileErr, baseErr) {
t.Errorf("FileError did not wrap base error")
}
}
@ -80,7 +80,7 @@ func TestAPIErrorUnwrap(t *testing.T) {
Message: "internal error",
Err: baseErr,
}
if unwrap := apiErr.Unwrap(); unwrap != baseErr {
t.Errorf("APIError.Unwrap() = %v, want %v", unwrap, baseErr)
if !errors.Is(apiErr, baseErr) {
t.Errorf("APIError.Unwrap() = %v, want %v", apiErr.Unwrap(), baseErr)
}
}

View File

@ -12,6 +12,7 @@ func Run(args []string) (string, error) {
cmdStr := "git " + strings.Join(args, " ")
logger.Debug("executing git command", "command", cmdStr, "args", args)
// nolint:gosec // intentional subprocess for git operation
out, err := exec.Command("git", args...).Output()
if err != nil {
logger.Error("git command failed",
@ -30,6 +31,7 @@ func Run(args []string) (string, error) {
func IsRepo() bool {
logger.Debug("checking if directory is a git repository")
// nolint:gosec // intentional subprocess for git repository check
_, err := exec.Command("git", "rev-parse", "--is-inside-work-tree").Output()
isRepo := err == nil
logger.Debug("git repository check completed", "is_repo", isRepo)

View File

@ -76,20 +76,23 @@ func (c *Client) streamInternal(messages []map[string]string, model string, temp
"stream": true,
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
// Manual cancel before os.Exit; otherwise defer is fine for the main path.
defer cancel()
body, err := json.Marshal(payload)
if err != nil {
logger.Error("failed to marshal API request", "error", err)
color.Red("Failed to marshal request: %v", err)
cancel()
os.Exit(1)
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
if err != nil {
logger.Error("failed to create HTTP request", "error", err, "url", url)
color.Red("Failed to create request: %v", err)
cancel()
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+c.APIKey)
@ -104,6 +107,7 @@ func (c *Client) streamInternal(messages []map[string]string, model string, temp
"model", model,
"duration_ms", time.Since(startTime).Milliseconds())
color.Red("Request failed: %v", err)
cancel()
os.Exit(1)
}
defer func() { _ = resp.Body.Close() }()

View File

@ -1,6 +1,7 @@
package linter
import (
"errors"
"fmt"
"os"
"os/exec"
@ -214,7 +215,7 @@ func FindAvailableLinter(lang *Language) (*Linter, error) {
}
// Build install instructions
var installOptions []string
installOptions := make([]string, 0, len(lang.Linters))
for _, linter := range lang.Linters {
installOptions = append(installOptions,
fmt.Sprintf(" - %s: %s", linter.Name, linter.InstallInfo))
@ -232,10 +233,12 @@ func RunLinter(filePath string, linter Linter) (*LintResult, error) {
"command", linter.Command)
// Build command arguments
args := append(linter.Args, filePath)
linterArgs := append([]string{}, linter.Args...)
linterArgs = append(linterArgs, filePath)
// Execute linter
cmd := exec.Command(linter.Command, args...)
// nolint:gosec // intentional subprocess for linter
cmd := exec.Command(linter.Command, linterArgs...)
output, err := cmd.CombinedOutput()
result := &LintResult{
@ -245,7 +248,8 @@ func RunLinter(filePath string, linter Linter) (*LintResult, error) {
}
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
result.ExitCode = exitErr.ExitCode()
result.HasIssues = true
logger.Info("linter found issues",
@ -302,7 +306,7 @@ func LintFile(filePath string) (*LintResult, error) {
// GetSupportedLanguages returns a list of all supported languages
func GetSupportedLanguages() []string {
var langs []string
langs := make([]string, 0, len(languages))
for _, lang := range languages {
langs = append(langs, fmt.Sprintf("%s (%s)", lang.Name, strings.Join(lang.Extensions, ", ")))
}

View File

@ -151,7 +151,7 @@ func main() {
fmt.Println("Hello, World!")
}
`
if err := os.WriteFile(testFile, []byte(validCode), 0644); err != nil {
if err := os.WriteFile(testFile, []byte(validCode), 0600); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
@ -207,7 +207,7 @@ func main() {
fmt.Println("Hello, World!")
}
`
if err := os.WriteFile(testFile, []byte(validCode), 0644); err != nil {
if err := os.WriteFile(testFile, []byte(validCode), 0600); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
@ -237,7 +237,7 @@ func main() {
t.Run("Lint unsupported file type", func(t *testing.T) {
testFile := filepath.Join(tmpDir, "test.txt")
if err := os.WriteFile(testFile, []byte("hello"), 0644); err != nil {
if err := os.WriteFile(testFile, []byte("hello"), 0600); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}

View File

@ -21,12 +21,12 @@ func Init(logLevel string) error {
}
logDir := filepath.Join(home, ".config", "grokkit")
if err := os.MkdirAll(logDir, 0755); err != nil {
if err := os.MkdirAll(logDir, 0750); err != nil {
return err
}
logFile := filepath.Join(logDir, "grokkit.log")
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return err
}

View File

@ -33,6 +33,7 @@ func TestVersionInfo(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
tt.check(t)
})
}

View File

@ -14,6 +14,7 @@ This document provides a table of contents for all tasks and features currently
* [8] [pprof.md](./queued/pprof.md) : grokkit agent pprof integration
* [9] [audit.md](./queued/audit.md) : grokkit audit
* [10] [git-chglog.md](./queued/git-chglog.md) : grokkit agent git-chglog integration
* [11] [admin.md](./queued/admin.md) : grokkit admin tool (to show token usage and other admin-only features)
* [99] [TODO_ITEM.md](./queued/TODO_ITEM.md) : TODO ITEM template
## Completed

View File

@ -1,5 +1,4 @@
# TODO ITEM 1
## Priority: 12 of 12
- [ ] 1 step one
- [ ] 2 step two
- [ ] 3 step three

19
todo/queued/admin.md Normal file
View File

@ -0,0 +1,19 @@
# `grokkit admin` administration tools
### Category: Nice to Have
Add an `admin` command that, if an `XAI_MGMT_KEY` is set in the environment, displays up-to-date token usage on the api account/team.
### Details
- **Command**: `grokkit admin`
- **Subcommands**:
- `grokkit admin token-usage` <-- default (shows token usage for current team)
- `grokkit admin credit-balance` <-- shows credit balance for current team
- `grokkit admin models` <-- shows available models
- `grokkit admin api-keys` <-- shows available API keys
- **Condition**: Must check for `XAI_MGMT_KEY` env var.
- **Functionality**: Fetch and display token usage stats from the [XAI MANAGEMENT API](https://docs.x.ai/developers/rest-api-reference/management.
- **Goal**: Help me monitor my API costs and limits directly from the CLI.
NOTE: If possible, it would be cool if this command was "hidden" if the XAI_MGMT_KEY is not set.

View File

@ -1,5 +1,4 @@
# `grokkit audit`
## Priority: 9 of 12
**Description**: Comprehensive AI-powered code audit for security, performance, best practices, and potential bugs across single files or entire projects.
**Benefits**:

View File

@ -1,5 +1,4 @@
# `grokkit agent` cnotes integration
## Priority: 6 of 12
**Description**: Wrappers for your `cnotes` CLI logging suite. Allows Grok to automatically log coding sessions, notes, progress during agent workflows (e.g., "start work on feature", "log bug found").

View File

@ -1,5 +1,4 @@
# `grokkit agent` git-chglog integration
## Priority: 11 of 12
**Description**: Wrapper for git-chglog CLI: generates CHANGELOG.md from conventional commits/tags. AI-enhanced parsing/validation.

View File

@ -1,5 +1,4 @@
# `grokkit agent` Go tools integration
## Priority: 5 of 12
**Description**: Wrappers for `go` subcommands: mod tidy, generate, vet, fmt. Ensures hygiene post-agent edits.

View File

@ -1,6 +1,5 @@
# Grokkit Interactive Agent
**Priority:** 2 of 12 (right after testgen)
## Goal

View File

@ -1,5 +1,4 @@
# `grokkit agent` make integration
## Priority: 3 of 12
**Description**: Wrappers for Makefile targets (test/lint/build/cover). Enables Grok to run/verify builds mid-agent workflow (e.g., "edit, test, fix loops").

View File

@ -1,5 +1,4 @@
# `grokkit agent` pprof integration
## Priority: 10 of 12
**Description**: Go pprof profiling wrappers (CPU/memory/allocs). Captures profiles during agent runs, AI-analyzes hotspots.

View File

@ -1,5 +1,4 @@
# `grokkit profile`
## Priority: 8 of 12
**Description**: Run Go benchmarks/pprof, get AI-suggested performance optimizations.
**Benefits**:

View File

@ -1,5 +1,4 @@
# `grokkit agent` ripgrep (rg) integration
## Priority: 7 of 12
**Description**: Fast search wrapper for ripgrep (`rg`). Enables agent to grep project for symbols/patterns/context before edits.

View File

@ -1,5 +1,4 @@
# `grokkit agent` tea integration
## Priority: 4 of 12
**Description**: Safe, AI-orchestrated wrappers for Gitea `tea` CLI commands. Enables Grok to manage repos (list/create PRs/issues, comments) as part of agent workflows, with previews and confirmations.