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
59 changes: 48 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,16 @@ selected provider, budget status, projects, and planned tasks. In interactive
terminals you are prompted for confirmation; in non-TTY environments (cron,
daemon, CI) confirmation is auto-skipped.

| Flag | Default | Description |
|------|---------|-------------|
| `--dry-run` | `false` | Show preflight summary and exit without executing |
| `--project`, `-p` | _(all configured)_ | Target a single project directory |
| `--task`, `-t` | _(auto-select)_ | Run a specific task by name |
| `--max-projects` | `1` | Max projects to process (ignored when `--project` is set) |
| `--max-tasks` | `1` | Max tasks per project (ignored when `--task` is set) |
| `--random-task` | `false` | Pick a random task from eligible tasks instead of the highest-scored one |
| `--ignore-budget` | `false` | Bypass budget checks (use with caution) |
| `--yes`, `-y` | `false` | Skip the confirmation prompt |
| Flag | Default | Description |
| ----------------- | ------------------ | ------------------------------------------------------------------------ |
| `--dry-run` | `false` | Show preflight summary and exit without executing |
| `--project`, `-p` | _(all configured)_ | Target a single project directory |
| `--task`, `-t` | _(auto-select)_ | Run a specific task by name |
| `--max-projects` | `1` | Max projects to process (ignored when `--project` is set) |
| `--max-tasks` | `1` | Max tasks per project (ignored when `--task` is set) |
| `--random-task` | `false` | Pick a random task from eligible tasks instead of the highest-scored one |
| `--ignore-budget` | `false` | Bypass budget checks (use with caution) |
| `--yes`, `-y` | `false` | Skip the confirmation prompt |

```bash
# Interactive run with preflight summary + confirmation prompt
Expand All @@ -141,6 +141,7 @@ nightshift run -p ./my-project -t lint-fix
```

Other useful flags:

- `nightshift status --today` to see today's activity summary
- `nightshift daemon start --foreground` for debug
- `--category` — filter tasks by category (pr, analysis, options, safe, map, emergency)
Expand All @@ -153,8 +154,9 @@ Other useful flags:
## Authentication (Subscriptions)

Nightshift supports three AI providers:

- **Claude Code** - Anthropic's Claude via local CLI
- **Codex** - OpenAI's GPT via local CLI
- **Codex** - OpenAI's GPT via local CLI
- **GitHub Copilot** - GitHub's Copilot via GitHub CLI

### Claude Code
Expand Down Expand Up @@ -258,6 +260,40 @@ Each task has a default cooldown interval to prevent the same task from running

## Development

### Package layout

Nightshift is organized as a small set of commands on top of focused internal
packages. Browse the godoc for any package with `go doc <import-path>` (or
`go doc ./...` for all of them).

| Package | Responsibility |
| -------------------------- | ---------------------------------------------------------------------------------- |
| `cmd/nightshift` | CLI entry point (`main`). |
| `cmd/nightshift/commands` | Cobra subcommands (`run`, `report`, `budget`, `setup`, `stats`, etc.). |
| `cmd/provider-calibration` | Standalone tool that estimates per-repo token usage from local agent session logs. |
| `internal/orchestrator` | The plan-implement-review loop that drives task execution. |
| `internal/agents` | Agent interface and CLI implementations (Claude, Codex, Copilot). |
| `internal/providers` | Provider interface for coding-agent backends and per-token cost. |
| `internal/tasks` | Task structures, cost/risk tiers, loading, and selection. |
| `internal/budget` | Token budget calculation and allocation across daily/weekly modes. |
| `internal/calibrator` | Infers weekly subscription budgets from usage history. |
| `internal/scheduler` | Cron, fixed-interval, and time-window job scheduling. |
| `internal/projects` | Multi-project discovery, config merging, and priority weighting. |
| `internal/integrations` | Readers for claude.md, agents.md, td, and GitHub issues. |
| `internal/config` | YAML config loading with environment overrides. |
| `internal/db` | SQLite storage, migrations, and legacy state import. |
| `internal/state` | Persistent run history driving staleness and de-duplication. |
| `internal/snapshots` | Periodic usage-snapshot collection. |
| `internal/trends` | Historical usage profiling, anomaly detection, and forecasting. |
| `internal/stats` | Aggregate statistics for the stats CLI. |
| `internal/reporting` | Markdown run reports and summaries. |
| `internal/security` | Credentials, sandboxing, audit logging, and safe defaults. |
| `internal/logging` | Structured zerolog logging with file rotation. |
| `internal/commits` | Conventional Commits message normalization. |
| `internal/tmux` | tmux session control and token-usage scraping. |
| `internal/analysis` | Git-based ownership and bus-factor analysis. |
| `internal/setup` | Interactive config and task-preset selection. |

### Pre-commit hooks

Install the git pre-commit hook to catch formatting and vet issues before pushing:
Expand All @@ -267,6 +303,7 @@ make install-hooks
```

This symlinks `scripts/pre-commit.sh` into `.git/hooks/pre-commit`. The hook runs:

- **gofmt** — flags any staged `.go` files that need formatting
- **go vet** — catches common correctness issues
- **go build** — ensures the project compiles
Expand Down
88 changes: 88 additions & 0 deletions cmd/nightshift/commands/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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 {
fmt.Fprintln(os.Stdout, normalized)
return nil
}
fmt.Fprintln(os.Stdout, normalized)
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
}
2 changes: 2 additions & 0 deletions cmd/provider-calibration/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Command provider-calibration analyzes local Codex and Claude session logs to
// estimate per-repository token usage, producing input for budget calibration.
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`.
3 changes: 2 additions & 1 deletion internal/agents/claude.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// claude.go implements the Agent interface for Claude Code CLI.
// This file implements the Agent interface for the Claude Code CLI.

package agents

import (
Expand Down
3 changes: 2 additions & 1 deletion internal/agents/codex.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// codex.go implements the Agent interface for OpenAI Codex CLI.
// This file implements the Agent interface for the OpenAI Codex CLI.

package agents

import (
Expand Down
3 changes: 2 additions & 1 deletion internal/agents/copilot.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// copilot.go implements the Agent interface for GitHub Copilot CLI.
// This file implements the Agent interface for the GitHub Copilot CLI.

package agents

import (
Expand Down
Loading