Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,17 @@ Each task has a default cooldown interval to prevent the same task from running

`skill-groom` is enabled by default. Add it to `tasks.disabled` if you want to opt out. It updates project-local skills under `.claude/skills` and `.codex/skills` using `README.md` as project context and starts Agent Skills docs lookup from `https://agentskills.io/llms.txt`.

## Guides

Deeper guides live in [`docs/guides/`](docs/guides/):

- [Adding Tasks](docs/guides/adding-tasks.md) — how to add a new built-in or custom task
- [Run Lifecycle](docs/guides/run-lifecycle.md) — what happens from a scheduled trigger to a finished run
- [Codex Budget Tracking](docs/guides/codex-budget-tracking.md) — how nightshift tracks and budgets Codex CLI usage
- [Provider Calibration](docs/guides/provider-calibration.md) — comparing Claude and Codex token behavior on your machine
- [Agent tmux Integration](docs/guides/agent-tmux-integration.md) — driving the CLIs via tmux
- [Website](docs/guides/website.md) — building and deploying this site

## Development

### Pre-commit hooks
Expand Down
92 changes: 92 additions & 0 deletions cmd/nightshift/commands/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package commands

import (
"fmt"
"io"
"os"

"github.com/marcus/nightshift/internal/commits"
"github.com/spf13/cobra"
)

var commitCmd = &cobra.Command{
Use: "commit",
Short: "Conventional Commits helpers",
Long: `Tools for working with Conventional Commits messages.

Use "commit normalize" to validate and reformat a commit message so it
follows the project's rules (type prefix, lowercase type, subject length,
and wrapped body).`,
}

var commitNormalizeCmd = &cobra.Command{
Use: "normalize [MESSAGE]",
Short: "Normalize a commit message to Conventional Commits format",
Long: `Validate and rewrite a commit message into canonical Conventional
Commits form.

The message is read from a positional argument, from a file passed via
--file (typically .git/COMMIT_EDITMSG by a commit-msg hook), or from stdin
when no argument and no --file are given.

nightshift commit normalize "feat: add login"
nightshift commit normalize --file .git/COMMIT_EDITMSG
git log -1 --pretty=%B | nightshift commit normalize

Use --check to only validate without rewriting; the exit code is non-zero
when the message does not conform.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
check, _ := cmd.Flags().GetBool("check")
file, _ := cmd.Flags().GetString("file")

raw, err := readCommitMessage(args, file)
if err != nil {
return err
}

normalized, err := commits.Normalize(raw)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return err
}

if check {
if _, err := fmt.Fprintln(os.Stdout, normalized); err != nil {
return err
}
return nil
}
if _, err := fmt.Fprintln(os.Stdout, normalized); err != nil {
return err
}
return nil
},
}

func init() {
commitNormalizeCmd.Flags().BoolP("check", "c", false, "Only validate; do not rewrite")
commitNormalizeCmd.Flags().StringP("file", "f", "", "Read the message from this file (use by the commit-msg hook)")
commitCmd.AddCommand(commitNormalizeCmd)
rootCmd.AddCommand(commitCmd)
}

// readCommitMessage resolves the message source in order: positional arg,
// --file, then stdin.
func readCommitMessage(args []string, file string) (string, error) {
if len(args) == 1 {
return args[0], nil
}
if file != "" {
b, err := os.ReadFile(file)
if err != nil {
return "", fmt.Errorf("read %s: %w", file, err)
}
return string(b), nil
}
b, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("read stdin: %w", err)
}
return string(b), nil
}
4 changes: 2 additions & 2 deletions cmd/nightshift/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ func printStruct(v reflect.Value, indent int) {
case reflect.Struct:
fmt.Printf("%s%s:\n", prefix, tag)
printStruct(value, indent+1)
case reflect.Ptr:
case reflect.Pointer:
if !value.IsNil() {
if value.Elem().Kind() == reflect.Struct {
fmt.Printf("%s%s:\n", prefix, tag)
Expand Down Expand Up @@ -408,7 +408,7 @@ func printStruct(v reflect.Value, indent int) {

func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Ptr, reflect.Interface:
case reflect.Pointer, reflect.Interface:
return v.IsNil()
case reflect.Slice, reflect.Map:
return v.Len() == 0
Expand Down
3 changes: 3 additions & 0 deletions cmd/provider-calibration/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Package main is the provider-calibration tool: it reads Claude and Codex
// session artifacts from local data directories and compares their token
// usage to help calibrate nightshift's budget assumptions.
package main

import (
Expand Down
47 changes: 47 additions & 0 deletions docs/commit-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Commit Messages

Nightshift uses [Conventional Commits](https://www.conventionalcommits.org/)
for all commit messages. This keeps the history readable and lets tooling
derive changelogs automatically.

## Format

```
<type>(<scope>): <subject>

<body>
```

- **type** — one of `feat`, `fix`, `docs`, `style`, `refactor`, `test`,
`chore`, `perf`, `build`, `ci`.
- **scope** — optional, e.g. `fix(api): ...`.
- **subject** — lowercase, imperative mood, no trailing period, max 72 chars.
- **body** — optional, wrapped at 72 columns, separated from the subject by a
blank line.

## The `commit normalize` command

Validate and reformat a message:

```sh
nightshift commit normalize "feat: add login screen"
nightshift commit normalize --file .git/COMMIT_EDITMSG
git log -1 --pretty=%B | nightshift commit normalize
```

Add `--check` to validate only. The command exits non-zero when a message
cannot be normalized (missing/unknown type, capitalized or overlong subject).

## commit-msg hook

To enforce the rules locally, install the hook:

```sh
make install-hooks
# or manually:
ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg
```

The hook normalizes your message file in place before the commit is created and
rejects messages that cannot be fixed automatically. Bypass it with
`git commit --no-verify`.
2 changes: 1 addition & 1 deletion docs/guides/adding-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Built-in Tasks

Nightshift ships with 50+ built-in task types organized into six categories. To add a new built-in task, modify the task registry in `internal/tasks/tasks.go`.
Nightshift ships with 59 built-in task types organized into six categories. To add a new built-in task, modify the task registry in `internal/tasks/tasks.go`.

### Step 1: Define the Task Type Constant

Expand Down
Loading